﻿using UnityEngine;
using System.Collections;
using System.Collections.Generic;
using System;
using System.Threading;
public class ConcurrentBatchQueue<T>
{
    private static readonly T m_Null = default(T);

    private object m_Entity;

    private Entity m_BackEntity;

    public bool IsEmpty { get { return Count <= 0; } }

    /// <summary>
    /// Gets the count.
    /// </summary>
    public int Count { get { return ((Entity)m_Entity).Count; } }

    class Entity
    {
        public T[] Array { get; set; }
        public int Count;
    }
    private Func<T, bool> m_NullValidator;

    public ConcurrentBatchQueue() : this(16) { }
    public ConcurrentBatchQueue(int capacity) : this(new T[capacity]) { }
    public ConcurrentBatchQueue(T[] array) : this(array, (t) => t == null) { }
    public ConcurrentBatchQueue(int capacity, Func<T, bool> nullValidator) : this(new T[capacity], nullValidator) { }
    public ConcurrentBatchQueue(T[] array, Func<T, bool> nullValidator)
    {
        var entity = new Entity();
        entity.Array = array;
        m_Entity = entity;
        m_BackEntity = new Entity();
        m_BackEntity.Array = new T[array.Length];
        m_NullValidator = nullValidator;
    }

    public bool Enqueue(T item)
    {
        bool full;
        while (true)
        {
            if (TryEnqueue(item, out full) || full)
                break;
        }

        return !full;
    }

    public bool TryEnqueue(T item, out bool full)
    {
        full = false;
        Entity entity = this.m_Entity as Entity;
        T[] array = entity.Array;
        int count = entity.Count;


        if (count >= array.Length)
        {
            full = true;
            return false;
        }
        int oldCount = Interlocked.CompareExchange(ref entity.Count, count + 1, count);
        if (oldCount != count)
        {
            return false;
        }
        array[count] = item;
        return true;
    }
    public bool Enqueue(IList<T> items)
    {
        bool full;
        while (true)
        {
            if (TryEnqueue(items, out full) || full)
                break;
        }
        return !full;
    }

    private bool TryEnqueue(IList<T> items, out bool full)
    {
        full = false;
        var entity = m_Entity as Entity;
        var array = entity.Array;
        var count = entity.Count;

        int newItemCount = items.Count;
        int expectedCount = count + newItemCount;
        if (expectedCount > array.Length)
        {
            full = true;
            return false;
        }
        int oldCount = Interlocked.CompareExchange(ref entity.Count, expectedCount, count);
        if (oldCount != count)
        {
            return false;
        }
        foreach (var item in items)
        {
            array[count++] = item;
        }
        return true;
    }

    public bool TryDequeue(IList<T> outputItems)
    {
        var entity = m_Entity as Entity;
        int count = entity.Count;
        if (count <= 0)
        {
            return false;
        }
        var oldEntity = Interlocked.CompareExchange(ref m_Entity, m_BackEntity, entity);
        if (!ReferenceEquals(oldEntity, entity))
        {
            return false;
        }
        Thread.SpinWait(1);

        count = entity.Count;
        var array = entity.Array;
        int i = 0;
        while (true)
        {
            T item = array[i];
            while (m_NullValidator(item))
            {
                Thread.SpinWait(1);
                item = array[i];
            }
            outputItems.Add(item);
            array[i] = m_Null;
            if (entity.Count <= (i + 1))
            {
                break;
            }
            i++;
        }
        entity.Count = 0;
        m_BackEntity = entity;
        return true;
    }
}